package top.z.template.parse;

import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.z.template.Error;
import top.z.template.Source;
import top.z.template.parse.Ast.Expression;
import top.z.template.parse.Ast.Node;
import top.z.template.parse.ast.*;
import top.z.template.parse.ast.expression.*;

import java.util.ArrayList;
import java.util.List;

public class Parser {
    private final static Logger logger = LoggerFactory.getLogger(Tokenizer.class);
    private final static Gson   gson   = new GsonBuilder()
            .serializeNulls()
            .addSerializationExclusionStrategy(new ExclusionStrategy() {
                @Override
                public boolean shouldSkipField(FieldAttributes f) {
                    return f.getName().equals("source");
                }

                @Override
                public boolean shouldSkipClass(Class<?> clazz) {
                    return false;
                }
            })
            .create();

    /**
     * 解析{@link Source}
     */
    public Result parse(Source source) {
        // 把模板的内容解析为 Token 集合
        List<Token> tokenList   = new Tokenizer().tokenize(source);
        TokenStream tokenStream = new TokenStream(tokenList);

        logger.info("{}", gson.toJson(tokenList));

        List<Ast.Node> node = new ArrayList<>();

        while (tokenStream.hasNext()) {
            node.add(parseStatement(tokenStream));
        }

        return new Result(node);
    }

    /**
     * 解析描述, 有可能是一个文本块, if语句, for语句, while语句, 宏定义, include或表达式
     */
    private Node parseStatement(TokenStream tokenStream) {
        Node result;

        if (tokenStream.match(TokenType.Text, false)) {
            result = new Text(tokenStream.consume().getSpan());
        } else if (tokenStream.match(Keyword.If.key, false)) {
            result = parseIfStatement(tokenStream);
        } else if (tokenStream.match(Keyword.For.key, false)) {
            result = parseForStatement(tokenStream);
        } else if (tokenStream.match(Keyword.Break.key, false)) {
            result = new Break(tokenStream.consume().getSpan());
        } else if (tokenStream.match(Keyword.While.key, false)) {
            result = parseWhileStatement(tokenStream);
            // } else if (tokenStream.match(Keyword.Include.key, false)) {
            //     result = parseInclude(tokenStream);
        } else if (tokenStream.match(Keyword.Return.key, false)) {
            result = parseReturn(tokenStream);
        } else if (tokenStream.match(Keyword.Continue.key, false)) {
            result = new Continue(tokenStream.consume().getSpan());
        } else {
            result = parseExpression(tokenStream);
        }
        // consume semi-colons as statement delimiters
        while (tokenStream.match(";", true))
            ;

        return result;
    }

    private If parseIfStatement(TokenStream stream) {
        Span openingIf = stream.expect(Keyword.If.key).getSpan();
        Expression condition = parseExpression(stream);
        List<Node> trueBlock = new ArrayList<>();
        // 匹配End, Else, ElseIf之前的数据
        while (stream.hasNext() && !stream.match(false, Keyword.End.key, Keyword.Else.key, Keyword.ElseIf.key)) {
            trueBlock.add(parseStatement(stream));
        }

        List<If> elseIfs = new ArrayList<>();
        while (stream.hasNext() && stream.match(false, Keyword.ElseIf.key)) {
            Span elseIfOpening = stream.expect(Keyword.ElseIf.key).getSpan();
            Expression elseIfCondition = parseExpression(stream);
            List<Node> elseIfBlock = new ArrayList<>();
            while (stream.hasNext() && !stream.match(false, Keyword.End.key, Keyword.Else.key, Keyword.ElseIf.key)) {
                elseIfBlock.add(parseStatement(stream));
            }
            Span elseIfSpan = new Span(elseIfOpening, !elseIfBlock.isEmpty() ? elseIfBlock.get(elseIfBlock.size() - 1).getSpan() : elseIfOpening);
            elseIfs.add(new If(elseIfSpan, elseIfCondition, elseIfBlock, new ArrayList<>(), new ArrayList<>()));
        }

        List<Node> falseBlock = new ArrayList<>();
        if (stream.match(Keyword.Else.key, true)) {
            while (stream.hasNext() && !stream.match(false, Keyword.End.key)) {
                falseBlock.add(parseStatement(stream));
            }
        }

        Span closingEnd = stream.expect(Keyword.End.key).getSpan();

        return new If(new Span(openingIf, closingEnd), condition, trueBlock, elseIfs, falseBlock);
    }

    private For parseForStatement(TokenStream stream) {
        Span openingFor = stream.expect("for").getSpan();

        Span index = null;
        Span value = stream.expect(TokenType.Identifier).getSpan();

        if (stream.match(TokenType.Comma, true)) {
            index = value;
            value = stream.expect(TokenType.Identifier).getSpan();
        }

        stream.expect("in");

        // 要循环的map或者list表达式(变量名称)
        Expression mapOrList = parseExpression(stream);

        List<Node> body = new ArrayList<>();
        while (stream.hasNext() && !stream.match(false, Keyword.End.key)) {
            body.add(parseStatement(stream));
        }

        Span closingEnd = stream.expect(Keyword.End.key).getSpan();

        return new For(new Span(openingFor, closingEnd), index, value, mapOrList, body);
    }

    private While parseWhileStatement(TokenStream stream) {
        Span openingWhile = stream.expect(Keyword.While.key).getSpan();

        Expression condition = parseExpression(stream);

        List<Node> body = new ArrayList<>();
        while (stream.hasNext() && !stream.match(false, Keyword.End.key)) {
            body.add(parseStatement(stream));
        }

        Span closingEnd = stream.expect(Keyword.End.key).getSpan();

        return new While(new Span(openingWhile, closingEnd), condition, body);
    }

    private Node parseReturn(TokenStream tokens) {
        Span returnSpan = tokens.expect(Keyword.Return.key).getSpan();
        if (tokens.match(";", false)) {
            return new Return(returnSpan, null);
        }
        Expression returnValue = parseExpression(tokens);
        return new Return(new Span(returnSpan, returnValue.getSpan()), returnValue);
    }

    private Expression parseExpression(TokenStream stream) {
        return parseTernaryOperator(stream);
    }

    private Expression parseTernaryOperator(TokenStream stream) {
        Expression condition = parseBinaryOperator(stream, 0);
        if (stream.match(TokenType.QuestionMark, true)) {
            Expression trueExpression = parseTernaryOperator(stream);
            stream.expect(TokenType.Colon);
            Expression falseExpression = parseTernaryOperator(stream);
            return new TernaryOperation(condition, trueExpression, falseExpression);
        } else {
            return condition;
        }
    }

    private Expression parseBinaryOperator(TokenStream stream, int level) {
        int        nextLevel = level + 1;
        Expression left;
        if (nextLevel == binaryOperatorPrecedence.length) {
            left = parseUnaryOperator(stream);
        } else {
            left = parseBinaryOperator(stream, nextLevel);
        }

        TokenType[] operators = binaryOperatorPrecedence[level];
        while (stream.hasNext() && stream.match(false, operators)) {
            Token      operator = stream.consume();
            Expression right    = nextLevel == binaryOperatorPrecedence.length ? parseUnaryOperator(stream) : parseBinaryOperator(stream, nextLevel);
            left = new BinaryOperation(left, operator, right);
        }
        return left;
    }

    private Expression parseUnaryOperator(TokenStream stream) {
        if (stream.match(false, unaryOperator)) {
            return new UnaryOperation(stream.consume(), parseUnaryOperator(stream));
        } else {
            if (stream.match(TokenType.LeftParentheses, true)) {
                Expression expression = parseExpression(stream);
                stream.expect(TokenType.RightParentheses);
                return expression;
            } else {
                return parseAccessOrCallOrLiteral(stream);
            }
        }
    }

    private Expression parseAccessOrCallOrLiteral(TokenStream stream) {
        //-----------------------------------------------------------------------------
        if (stream.match(TokenType.LeftCurly, false)) {
            return parseMapLiteral(stream);
        }
        if (stream.match(TokenType.Identifier, false)) {
            return parseAccessOrCall(stream);
        }
        if (stream.match(TokenType.LeftBracket, false)) {
            return parseListLiteral(stream);
        }
        //-----------------------------------------------------------------------------
        if (stream.match(TokenType.Null, false)) {
            return new NullLiteral(stream.expect(TokenType.Null).getSpan());
        }
        if (stream.match(TokenType.Byte, false)) {
            return new ByteLiteral(stream.expect(TokenType.Byte).getSpan());
        }
        if (stream.match(TokenType.Long, false)) {
            return new LongLiteral(stream.expect(TokenType.Long).getSpan());
        }
        if (stream.match(TokenType.Short, false)) {
            return new ShortLiteral(stream.expect(TokenType.Short).getSpan());
        }
        if (stream.match(TokenType.Float, false)) {
            return new FloatLiteral(stream.expect(TokenType.Float).getSpan());
        }
        if (stream.match(TokenType.Double, false)) {
            return new DoubleLiteral(stream.expect(TokenType.Double).getSpan());
        }
        if (stream.match(TokenType.String, false)) {
            return new StringLiteral(stream.expect(TokenType.String).getSpan());
        }
        if (stream.match(TokenType.Boolean, false)) {
            return new BooleanLiteral(stream.expect(TokenType.Boolean).getSpan());
        }
        if (stream.match(TokenType.Integer, false)) {
            return new IntegerLiteral(stream.expect(TokenType.Integer).getSpan());
        }
        if (stream.match(TokenType.Character, false)) {
            return new CharacterLiteral(stream.expect(TokenType.Character).getSpan());
        }

        throw Error.error("Expected a variable, field, map, array, function or method call, or literal.", stream);
    }

    private Expression parseAccessOrCall(TokenStream stream) {
        Span       identifier = stream.expect(TokenType.Identifier).getSpan();
        Expression result     = new Variable(identifier);

        while (stream.hasNext() && stream.match(false, TokenType.Dot, TokenType.LeftBracket, TokenType.LeftParentheses)) {
            // function or method call
            if (stream.match(TokenType.LeftParentheses, false)) {
                List<Expression> arguments   = parseArgument(stream);
                Span             closingSpan = stream.expect(TokenType.RightParentheses).getSpan();
                if (result instanceof Variable || result instanceof MapOrArrayAccess) {
                    result = new FunctionCall(new Span(result.getSpan(), closingSpan), result, arguments);
                } else if (result instanceof MemberAccess) {
                    result = new MethodCall(new Span(result.getSpan(), closingSpan), (MemberAccess) result, arguments);
                } else {
                    throw Error.error("Expected a variable, field or method.", stream);
                }
            }
            // map or array access
            else if (stream.match(TokenType.LeftBracket, true)) {
                Expression keyOrIndex  = parseExpression(stream);
                Span       closingSpan = stream.expect(TokenType.RightBracket).getSpan();
                result = new MapOrArrayAccess(new Span(result.getSpan(), closingSpan), result, keyOrIndex);
            }
            // field or method access
            else if (stream.match(TokenType.Dot, true)) {
                identifier = stream.expect(TokenType.Identifier).getSpan();
                result = new MemberAccess(result, identifier);
            }
        }

        return result;
    }

    private Expression parseMapLiteral(TokenStream stream) {
        Span openCurly = stream.expect(TokenType.LeftCurly).getSpan();

        List<Span>       key    = new ArrayList<>();
        List<Expression> values = new ArrayList<>();
        while (stream.hasNext() && !stream.match(TokenType.RightCurly, false)) {
            key.add(stream.expect(TokenType.Identifier).getSpan());
            stream.expect(":");
            values.add(parseExpression(stream));
            if (!stream.match(TokenType.RightCurly, false)) stream.expect(TokenType.Comma);
        }

        Span closeCurly = stream.expect(TokenType.RightCurly).getSpan();
        return new MapLiteral(new Span(openCurly, closeCurly), key, values);
    }

    private Expression parseListLiteral(TokenStream stream) {
        Span openBracket = stream.expect(TokenType.LeftBracket).getSpan();

        List<Expression> values = new ArrayList<>();
        while (stream.hasNext() && !stream.match(TokenType.RightBracket, false)) {
            values.add(parseExpression(stream));
            if (!stream.match(TokenType.RightBracket, false)) stream.expect(TokenType.Comma);
        }

        Span closeBracket = stream.expect(TokenType.RightBracket).getSpan();
        return new ListLiteral(new Span(openBracket, closeBracket), values);
    }

    /**
     * 不使用右括号
     */
    private List<Expression> parseArgument(TokenStream stream) {
        stream.expect(TokenType.LeftParentheses);
        List<Expression> arguments = new ArrayList<>();
        while (stream.hasNext() && !stream.match(TokenType.RightParentheses, false)) {
            arguments.add(parseExpression(stream));
            if (!stream.match(TokenType.RightParentheses, false)) {
                stream.expect(TokenType.Comma);
            }
        }
        return arguments;
    }

    TokenType[] unaryOperator = new TokenType[]{
            TokenType.Not,
            TokenType.Plus,
            TokenType.Minus
    };

    TokenType[][] binaryOperatorPrecedence = new TokenType[][]{
            new TokenType[]{
                    TokenType.Assignment, // =
            },
            new TokenType[]{
                    TokenType.EQ,         // ==
                    TokenType.NQ          // !=
            },
            new TokenType[]{
                    TokenType.Or,         // ||
                    TokenType.Xor,        // ^
                    TokenType.And,        // &&
            },
            new TokenType[]{
                    TokenType.LT,         // <
                    TokenType.LE,         // <=
                    TokenType.GT,         // >
                    TokenType.GE,         // >=
            },
            new TokenType[]{
                    TokenType.Plus,       // +
                    TokenType.Minus,      // -
                    TokenType.Divide,     // /
                    TokenType.Percent,    // %
                    TokenType.Multiply,   // *
            }
    };

    public static class Result {

        private final List<Ast.Node> node;

        public Result(List<Ast.Node> list) {
            this.node = list;
        }

        public List<Ast.Node> getNode() {
            return node;
        }
    }

    public enum Keyword {
        If("if"),
        For("for"),
        End("end"),
        Else("else"),
        Break("break"),
        While("while"),
        ElseIf("elseif"),
        Return("return"),
        Include("include"),
        Continue("continue"),
        ;

        private final String key;

        Keyword(String key) {
            this.key = key;
        }

        public String key() {
            return key;
        }
    }

}
